
#ifndef _IFORMULA_H_
#define _IFORMULA_H_

#include <BString>

namespace BWE
{
	template<class T>
	class IFormula : public BThing
	{
		class Exp
		{
		public:
			inline Exp(char type)
			{
				this->type = type;
				this->value = 0;
			}
			inline ~Exp()
			{
				for (int i = 0; i < exps.size(); i++)
				{
					delete exps[i];
				}
				exps.clear();
			}
			inline void merge(int& index)
			{
				Exp* prev = exps[index - 1];
				Exp* exp = exps[index];
				Exp* next = exps[index + 1];
				T v0 = (*prev)();
				T v1 = (*next)();
				switch (exp->type)
				{
				case '*':
					value = v0 * v1; break;
				case '/':
					value = v0 / v1; break;
				case '+':
					value = v0 + v1; break;
				case '-':
					value = v0 - v1; break;
				default:
					return;
				}
				prev->type = 0;
				prev->value = value;
				delete exp;
				delete next;
				exps.remove(index, 2);
				index--;
			}
			inline T operator()()
			{
				if (exps.empty())
					return value;
				if (exps.size() == 1)
				{
					value = exps[0]->value;
					for (int i = 0; i < exps.size(); i++)
						delete exps[i];
					exps.clear();
					return value;
				}
				if (exps[0]->type == '-' && exps[1]->type == 0)
				{
					value = -exps[1]->value;
					exps[1]->value = value;
					exps.remove(0);
				}
				if (exps[0]->type == '+' && exps[1]->type == 0)
				{
					value = exps[1]->value;
					exps.remove(0);
				}
				for (int i = 1; i < exps.size() - 1; i++)
				{
					Exp* exp = exps[i];
					if (exp->type == '*' || exp->type == '/')
					{
						merge(i);
					}
				}
				for (int i = 1; i < exps.size() - 1; i++)
				{
					Exp* exp = exps[i];
					if (exp->type == '+' || exp->type == '-')
					{
						merge(i);
					}
				}
				if (exps.size() == 2)
				{
					value = (T)0;
					if (exps[0]->type == 0)
						value = exps[0]->value;
					if (exps[1]->type == 0)
						value = exps[1]->value;
				}
				for (int i = 0; i < exps.size(); i++)
					delete exps[i];
				exps.clear();
				return value;
			}
			char			type;
			T				value;
			BArray<Exp*>	exps;
		};

	public:
		inline IFormula()
		{
			_error = false;
			_isreal = std::is_floating_point<T>::value;
		}
		inline ~IFormula()
		{
			_error = false;
		}

		inline T operator()(const BString& text)
		{
			_result = 0;
			_error = false;
			_errorStr.clear();
			int pos = 0;
			Exp* root = create(text, pos, text.size());
			if (_error)
			{
				if (root)
					delete root;
				return _result;
			}
			if (root)
			{
				_result = (*root)();
				delete root;
			}
			return _result;
		}

		inline bool error() const
		{
			return _error;
		}

	protected:
		inline int pick_word(const BString& text, BString& word, int pos) const
		{
			word.reset();
			int size = text.size();
			const char* cstr = text.cstr();
			if (pos < 0 || pos >= size)
				return size;
			while (pos < size)
			{
				char c = cstr[pos];
				if (!isspace(c))
					break;
				pos++;
			}
			if (pos == size)
				return size;
			if (isdigit(cstr[pos]))
			{
				bool isreal = _isreal;
				int end = pos;
				while (++end < size)
				{
					if (isreal && cstr[end] == '.')
					{
						isreal = false;
						continue;
					}
					if (cstr[end] == '.')
						break;
					if (!isdigit(cstr[end]))
						break;
				}
				word.append(text, pos, end);
				return end;
			}
			if (ispunct(cstr[pos]))
			{
				int end = pos;
				while (end < size)
				{
					if (isspace(cstr[end]))
						break;
					if (!ispunct(cstr[end]))
						break;
					end++;
				}
				word.append(text, pos, end);
				return end;
			}
			int end = pos;
			while (end < size)
			{
				if (isspace(cstr[end]))
					break;
				if (ispunct(cstr[end]))
					break;
				end++;
			}
			word.append(text, pos, end);
			return end;
		}
		inline char pick_next_opera(const BString& text, int& pos) const
		{
			const char* cstr = text.cstr();
			while (pos < text.size())
			{
				if (!isspace(cstr[pos]))
					break;
				pos++;
			}
			if (pos < text.size())
			{
				char c = cstr[pos];
				if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
					return c;
				if (c >= '0' && c <= '9')
					return 0;
			}
			return -1;
		}
		inline int find_unit_end(const BString& text, int pos) const
		{
			int level = 0;
			const char* cstr = text.cstr();
			while (pos < text.size())
			{
				if (cstr[pos] == ')')
					break;
				pos++;
			}
			return pos;
		}
		inline Exp* create(const BString& text, int& pos, int end) const
		{
			Exp* exp = new Exp(0);
			BString word;
			while (pos < end)
			{
				char opera = pick_next_opera(text, pos);
				if (opera < 0)
					break;
				if (opera > 0)
				{
					if (opera == '(')
					{
						int unit_end = find_unit_end(text, pos);
						if (unit_end < end)
						{
							pos++;
							Exp* childItem = create(text, pos, unit_end);
							exp->exps.append(childItem);
							pos = unit_end + 1;
						}
						else
						{
							_error = true;
							_errorStr << "error : " << pos << " - unit has no end.";
							break;
						}
					}
					else
					{
						Exp* childItem = new Exp(opera);
						exp->exps.append(childItem);
						pos++;
					}
				}
				else
				{
					pos = pick_word(text, word, pos);
					if (word.isNumber())
					{
						T value = 0;
						const std::type_info* ti = &typeid(T);
						if (*ti == typeid(int))
							value = (T)word.toInt();
						else if (*ti == typeid(double))
							value = (T)word.toDouble();
						else if (*ti == typeid(float))
							value = (T)word.toFloat();
						else if (*ti == typeid(long long))
							value = (T)word.toLong();
						Exp* childItem = new Exp(0);
						childItem->value = value;
						exp->exps.append(childItem);
					}
					else
					{
						_error = true;
						_errorStr << "error : " << pos << " - unkonwn opera[" << word << ']';
						break;
					}
				}
			}
			return exp;
		}

	private:
		T				_result;
		bool			_isreal;
		mutable bool	_error;
		mutable BString	_errorStr;
	};
}

#endif